package hos.biometric;

/**
 * <p>Title: BiometricManager </p>
 * <p>Description:  </p>
 * <p>Company: www.mapuni.com </p>
 *
 * @author : 蔡俊峰
 * @version : 1.0
 * @date : 2022/11/18 10:19
 */

import android.annotation.SuppressLint;
import android.app.KeyguardManager;
import android.content.Context;
import android.os.Build;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.annotation.VisibleForTesting;
import android.util.Log;


import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * A class that provides system information related to biometrics (e.g. fingerprint, face, etc.).
 *
 * <p>On devices running Android 10 (API 29) and above, this will query the framework's version of
 * {@link android.hardware.biometrics.BiometricManager}. On Android 9.0 (API 28) and prior
 * versions, this will query {@link android.support.v4.hardware.fingerprint.FingerprintManagerCompat}.
 *
 * @see BiometricPrompt To prompt the user to authenticate with their biometric.
 */
@SuppressWarnings("deprecation")
public class BiometricManager {
 private static final String TAG = "BiometricManager";

 /**
  * The user can successfully authenticate.
  */
 public static final int BIOMETRIC_SUCCESS = 0;

 /**
  * Unable to determine whether the user can authenticate.
  *
  * <p>This status code may be returned on older Android versions due to partial incompatibility
  * with a newer API. Applications that wish to enable biometric authentication on affected
  * devices may still call {@code BiometricPrompt#authenticate()} after receiving this status
  * code but should be prepared to handle possible errors.
  */
 public static final int BIOMETRIC_STATUS_UNKNOWN = -1;

 /**
  * The user can't authenticate because the specified options are incompatible with the current
  * Android version.
  */
 public static final int BIOMETRIC_ERROR_UNSUPPORTED = -2;

 /**
  * The user can't authenticate because the hardware is unavailable. Try again later.
  */
 public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1;

 /**
  * The user can't authenticate because no biometric or device credential is enrolled.
  */
 public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11;

 /**
  * The user can't authenticate because there is no suitable hardware (e.g. no biometric sensor
  * or no keyguard).
  */
 public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12;

 /**
  * The user can't authenticate because a security vulnerability has been discovered with one or
  * more hardware sensors. The affected sensor(s) are unavailable until a security update has
  * addressed the issue.
  */
 public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15;

 /**
  * A status code that may be returned when checking for biometric authentication.
  */
 @IntDef({
         BIOMETRIC_SUCCESS,
         BIOMETRIC_STATUS_UNKNOWN,
         BIOMETRIC_ERROR_UNSUPPORTED,
         BIOMETRIC_ERROR_HW_UNAVAILABLE,
         BIOMETRIC_ERROR_NONE_ENROLLED,
         BIOMETRIC_ERROR_NO_HARDWARE,
         BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
 })
 @Retention(RetentionPolicy.SOURCE)
 @interface AuthenticationStatus {}

 /**
  * Types of authenticators, defined at a level of granularity supported by
  * {@link BiometricManager} and {@link BiometricPrompt}.
  *
  * <p>Types may combined via bitwise OR into a single integer representing multiple
  * authenticators (e.g. {@code DEVICE_CREDENTIAL | BIOMETRIC_WEAK}).
  *
  * @see #canAuthenticate(int)
  * @see BiometricPrompt.PromptInfo.Builder#setAllowedAuthenticators(int)
  */
 public interface Authenticators {
  /**
   * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
   * requirements for <strong>Class 3</strong> (formerly <strong>Strong</strong>), as defined
   * by the Android CDD.
   */
  int BIOMETRIC_STRONG = 0x000F;

  /**
   * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
   * requirements for <strong>Class 2</strong> (formerly <strong>Weak</strong>), as defined by
   * the Android CDD.
   *
   * <p>Note that this is a superset of {@link #BIOMETRIC_STRONG} and is defined such that
   * {@code BIOMETRIC_STRONG | BIOMETRIC_WEAK == BIOMETRIC_WEAK}.
   */
  int BIOMETRIC_WEAK = 0x00FF;

  /**
   * The non-biometric credential used to secure the device (i.e. PIN, pattern, or password).
   * This should typically only be used in combination with a biometric auth type, such as
   * {@link #BIOMETRIC_WEAK}.
   */
  int DEVICE_CREDENTIAL = 1 << 15;
 }

 /**
  * A bitwise combination of authenticator types defined in {@link Authenticators}.
  */
 @IntDef(flag = true, value = {
         Authenticators.BIOMETRIC_STRONG,
         Authenticators.BIOMETRIC_WEAK,
         Authenticators.DEVICE_CREDENTIAL
 })
 @Retention(RetentionPolicy.SOURCE)
 @interface AuthenticatorTypes {}

 /**
  * An injector for various class and method dependencies. Used for testing.
  */
 @VisibleForTesting
 interface Injector {
  /**
   * Provides the framework biometric manager that may be used on Android 10 (API 29) and
   * above.
   *
   * @return An instance of {@link android.hardware.biometrics.BiometricManager}.
   */
  @RequiresApi(Build.VERSION_CODES.Q)
  @Nullable
  android.hardware.biometrics.BiometricManager getBiometricManager();

  /**
   * Provides the fingerprint manager that may be used on Android 9.0 (API 28) and below.
   *
   * @return An instance of
   * {@link android.support.v4.hardware.fingerprint.FingerprintManagerCompat}.
   */
  @Nullable
  android.support.v4.hardware.fingerprint.FingerprintManagerCompat getFingerprintManager();

  /**
   * Checks if the current device is capable of being secured with a lock screen credential
   * (i.e. PIN, pattern, or password).
   */
  boolean isDeviceSecurable();

  /**
   * Checks if the current device is secured with a lock screen credential (i.e. PIN, pattern,
   * or password).
   *
   * @return Whether the device is secured with a lock screen credential.
   */
  boolean isDeviceSecuredWithCredential();

  /**
   * Checks if the current device has a hardware sensor that may be used for fingerprint
   * authentication.
   *
   * @return Whether the device has a fingerprint sensor.
   */
  boolean isFingerprintHardwarePresent();

  /**
   * Checks if all biometric sensors on the device are known to meet or exceed the security
   * requirements for <strong>Class 3</strong> (formerly <strong>Strong</strong>).
   *
   * @return Whether all biometrics are known to be <strong>Class 3</strong> or stronger.
   */
  boolean isStrongBiometricGuaranteed();
 }

 /**
  * Provides the default class and method dependencies that will be used in production.
  */
 private static class DefaultInjector implements Injector {
  @NonNull
  private final Context mContext;

  /**
   * Creates a default injector from the given context.
   *
   * @param context The application or activity context.
   */
  DefaultInjector(@NonNull Context context) {
   mContext = context.getApplicationContext();
  }

  @Override
  @RequiresApi(Build.VERSION_CODES.Q)
  @Nullable
  public android.hardware.biometrics.BiometricManager getBiometricManager() {
   return Api29Impl.create(mContext);
  }

  @Override
  @Nullable
  public android.support.v4.hardware.fingerprint.FingerprintManagerCompat getFingerprintManager() {
   return android.support.v4.hardware.fingerprint.FingerprintManagerCompat.from(mContext);
  }

  @Override
  public boolean isDeviceSecurable() {
   return KeyguardUtils.getKeyguardManager(mContext) != null;
  }

  @Override
  public boolean isDeviceSecuredWithCredential() {
   return KeyguardUtils.isDeviceSecuredWithCredential(mContext);
  }

  @Override
  public boolean isFingerprintHardwarePresent() {
   return PackageUtils.hasSystemFeatureFingerprint(mContext);
  }

  @Override
  public boolean isStrongBiometricGuaranteed() {
   return DeviceUtils.canAssumeStrongBiometrics(mContext, Build.MODEL);
  }
 }

 /**
  * The injector for class and method dependencies used by this manager.
  */
 @NonNull private final Injector mInjector;

 /**
  * The framework biometric manager. Should be non-null on Android 10 (API 29) and above.
  */
 @Nullable private final android.hardware.biometrics.BiometricManager mBiometricManager;

 /**
  * The framework fingerprint manager. Should be non-null on Android 10 (API 29) and below.
  */
 @Nullable private final android.support.v4.hardware.fingerprint.FingerprintManagerCompat
         mFingerprintManager;

 /**
  * Creates a {@link BiometricManager} instance from the given context.
  *
  * @param context The application or activity context.
  * @return An instance of {@link BiometricManager}.
  */
 @NonNull
 public static BiometricManager from(@NonNull Context context) {
  return new BiometricManager(new DefaultInjector(context));
 }

 /**
  * Creates a {@link BiometricManager} instance with the given injector.
  *
  * @param injector An injector for class and method dependencies.
  */
 @VisibleForTesting
 BiometricManager(@NonNull Injector injector) {
  mInjector = injector;
  mBiometricManager = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
          ? injector.getBiometricManager()
          : null;
  mFingerprintManager = Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q
          ? injector.getFingerprintManager()
          : null;
 }

 /**
  * Checks if the user can authenticate with biometrics. This requires at least one biometric
  * sensor to be present, enrolled, and available on the device.
  *
  * @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with biometrics. Otherwise,
  * returns an error code indicating why the user can't authenticate, or
  * {@link #BIOMETRIC_STATUS_UNKNOWN} if it is unknown whether the user can authenticate.
  *
  * @deprecated Use {@link #canAuthenticate(int)} instead.
  */
 @Deprecated
 @AuthenticationStatus
 public int canAuthenticate() {
  return canAuthenticate(Authenticators.BIOMETRIC_WEAK);
 }

 /**
  * Checks if the user can authenticate with an authenticator that meets the given requirements.
  * This requires at least one of the specified authenticators to be present, enrolled, and
  * available on the device.
  *
  * <p>Note that not all combinations of authenticator types are supported prior to Android 11
  * (API 30). Specifically, {@code DEVICE_CREDENTIAL} alone is unsupported prior to API 30, and
  * {@code BIOMETRIC_STRONG | DEVICE_CREDENTIAL} is unsupported on API 28-29. Developers that
  * wish to check for the presence of a PIN, pattern, or password on these versions should
  * instead use {@link KeyguardManager#isDeviceSecure()}.
  *
  * @param authenticators A bit field representing the types of {@link Authenticators} that may
  *                       be used for authentication.
  * @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with an allowed
  * authenticator. Otherwise, returns {@link #BIOMETRIC_STATUS_UNKNOWN} or an error code
  * indicating why the user can't authenticate.
  */
 @AuthenticationStatus
 public int canAuthenticate(@AuthenticatorTypes int authenticators) {
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
   if (mBiometricManager == null) {
    Log.e(TAG, "Failure in canAuthenticate(). BiometricManager was null.");
    return BIOMETRIC_ERROR_HW_UNAVAILABLE;
   }
   return Api30Impl.canAuthenticate(mBiometricManager, authenticators);
  }
  return canAuthenticateCompat(authenticators);
 }

 /**
  * Checks if the user can authenticate with an authenticator that meets the given requirements.
  *
  * <p>This method attempts to emulate the behavior of {@link #canAuthenticate(int)} on devices
  * running Android 10 (API 29) and below.
  *
  * @param authenticators A bit field representing the types of {@link Authenticators} that may
  *                       be used for authentication.
  * @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with the given set of allowed
  * authenticators. Otherwise, returns an error code indicating why the user can't authenticate,
  * or {@link #BIOMETRIC_STATUS_UNKNOWN} if it is unknown whether the user can authenticate.
  */
 @AuthenticationStatus
 private int canAuthenticateCompat(@AuthenticatorTypes int authenticators) {
  if (!AuthenticatorUtils.isSupportedCombination(authenticators)) {
   return BIOMETRIC_ERROR_UNSUPPORTED;
  }

  // Match the framework's behavior for an empty authenticator set on API 30.
  if (authenticators == 0) {
   return BIOMETRIC_ERROR_NO_HARDWARE;
  }

  // Authentication is impossible if the device can't be secured.
  if (!mInjector.isDeviceSecurable()) {
   return BIOMETRIC_ERROR_NO_HARDWARE;
  }

  // Credential authentication is always possible if the device is secured. Conversely, no
  // form of authentication is possible if the device is not secured.
  if (AuthenticatorUtils.isDeviceCredentialAllowed(authenticators)) {
   return mInjector.isDeviceSecuredWithCredential()
           ? BIOMETRIC_SUCCESS
           : BIOMETRIC_ERROR_NONE_ENROLLED;
  }

  // The class of some non-fingerprint biometrics can be checked on API 29.
  if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
   return AuthenticatorUtils.isWeakBiometricAllowed(authenticators)
           ? canAuthenticateWithWeakBiometricOnApi29()
           : canAuthenticateWithStrongBiometricOnApi29();
  }

  // Non-fingerprint biometrics may be invoked but can't be checked on API 28.
  if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) {
   // Having fingerprint hardware is a prerequisite, since BiometricPrompt internally
   // calls FingerprintManager#getErrorString() on API 28 (b/151443237).
   return mInjector.isFingerprintHardwarePresent()
           ? canAuthenticateWithFingerprintOrUnknownBiometric()
           : BIOMETRIC_ERROR_NO_HARDWARE;
  }

  // No non-fingerprint biometric APIs exist prior to API 28.
  return canAuthenticateWithFingerprint();
 }

 /**
  * Checks if the user can authenticate with a <strong>Class 3</strong> (formerly
  * <strong>Strong</strong>) or better biometric sensor on a device running Android 10 (API 29).
  *
  * @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with a
  * <strong>Class 3</strong> or better biometric sensor. Otherwise, returns an error code
  * indicating why the user can't authenticate, or {@link #BIOMETRIC_STATUS_UNKNOWN} if it is
  * unknown whether the user can authenticate.
  */
 @RequiresApi(Build.VERSION_CODES.Q)
 @AuthenticationStatus
 private int canAuthenticateWithStrongBiometricOnApi29() {
  // Use the hidden canAuthenticate(CryptoObject) method if it exists.
  final Method canAuthenticateWithCrypto = Api29Impl.getCanAuthenticateWithCryptoMethod();
  if (canAuthenticateWithCrypto != null) {
   final android.hardware.biometrics.BiometricPrompt.CryptoObject crypto =
           CryptoObjectUtils.wrapForBiometricPrompt(
                   CryptoObjectUtils.createFakeCryptoObject());
   if (crypto != null) {
    try {
     final Object result =
             canAuthenticateWithCrypto.invoke(mBiometricManager, crypto);
     if (result instanceof Integer) {
      return (int) result;
     }
     Log.w(TAG, "Invalid return type for canAuthenticate(CryptoObject).");
    } catch (IllegalAccessException | IllegalArgumentException
            | InvocationTargetException e) {
     Log.w(TAG, "Failed to invoke canAuthenticate(CryptoObject).", e);
    }
   }
  }

  // Check if we can use canAuthenticate() as a proxy for canAuthenticate(BIOMETRIC_STRONG).
  @AuthenticationStatus final int result = canAuthenticateWithWeakBiometricOnApi29();
  if (mInjector.isStrongBiometricGuaranteed() || result != BIOMETRIC_SUCCESS) {
   return result;
  }

  // If all else fails, check if fingerprint authentication is available.
  return canAuthenticateWithFingerprintOrUnknownBiometric();
 }

 /**
  * Checks if the user can authenticate with a <strong>Class 2</strong> (formerly
  * <strong>Weak</strong>) or better biometric sensor on a device running Android 10 (API 29).
  *
  * @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with a
  * <strong>Class 2</strong> or better biometric sensor. Otherwise, returns an error code
  * indicating why the user can't authenticate.
  */
 @RequiresApi(Build.VERSION_CODES.Q)
 @AuthenticationStatus
 private int canAuthenticateWithWeakBiometricOnApi29() {
  if (mBiometricManager == null) {
   Log.e(TAG, "Failure in canAuthenticate(). BiometricManager was null.");
   return BIOMETRIC_ERROR_HW_UNAVAILABLE;
  }
  return Api29Impl.canAuthenticate(mBiometricManager);
 }

 /**
  * Checks if the user can authenticate with fingerprint or with a biometric sensor for which
  * there is no platform method to check availability.
  *
  * @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with fingerprint. Otherwise,
  * returns an error code indicating why the user can't authenticate, or
  * {@link #BIOMETRIC_STATUS_UNKNOWN} if it is unknown whether the user can authenticate.
  */
 @AuthenticationStatus
 private int canAuthenticateWithFingerprintOrUnknownBiometric() {
  // If the device is not secured, authentication is definitely not possible. Use
  // FingerprintManager to distinguish between the "no hardware" and "none enrolled" cases.
  if (!mInjector.isDeviceSecuredWithCredential()) {
   return canAuthenticateWithFingerprint();
  }

  // Check for definite availability of fingerprint. Otherwise, return "unknown" to allow for
  // non-fingerprint biometrics (e.g. iris) that may be available via BiometricPrompt.
  return canAuthenticateWithFingerprint() == BIOMETRIC_SUCCESS
          ? BIOMETRIC_SUCCESS
          : BIOMETRIC_STATUS_UNKNOWN;
 }

 /**
  * Checks if the user can authenticate with fingerprint.
  *
  * @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with fingerprint.
  * Otherwise, returns an error code indicating why the user can't authenticate.
  */
 @AuthenticationStatus
 private int canAuthenticateWithFingerprint() {
  if (mFingerprintManager == null) {
   Log.e(TAG, "Failure in canAuthenticate(). FingerprintManager was null.");
   return BIOMETRIC_ERROR_HW_UNAVAILABLE;
  }
  if (!mFingerprintManager.isHardwareDetected()) {
   return BIOMETRIC_ERROR_NO_HARDWARE;
  }
  if (!mFingerprintManager.hasEnrolledFingerprints()) {
   return BIOMETRIC_ERROR_NONE_ENROLLED;
  }
  return BIOMETRIC_SUCCESS;
 }

 /**
  * Nested class to avoid verification errors for methods introduced in Android 11 (API 30).
  */
 @RequiresApi(Build.VERSION_CODES.R)
 private static class Api30Impl {
  // Prevent instantiation.
  private Api30Impl() {}

  /**
   * Calls {@link android.hardware.biometrics.BiometricManager#canAuthenticate(int)} for the
   * given biometric manager and set of allowed authenticators.
   *
   * @param biometricManager An instance of
   *                         {@link android.hardware.biometrics.BiometricManager}.
   * @param authenticators   A bit field representing the types of {@link Authenticators} that
   *                         may be used for authentication.
   * @return The result of
   * {@link android.hardware.biometrics.BiometricManager#canAuthenticate(int)}.
   */
  @SuppressLint("WrongConstant")
  @AuthenticationStatus
  static int canAuthenticate(
          @NonNull android.hardware.biometrics.BiometricManager biometricManager,
          @AuthenticatorTypes int authenticators) {
   return biometricManager.canAuthenticate(authenticators);
  }
 }

 /**
  * Nested class to avoid verification errors for methods introduced in Android 10 (API 29).
  */
 @RequiresApi(Build.VERSION_CODES.Q)
 private static class Api29Impl {
  // Prevent instantiation.
  private Api29Impl() {}

  /**
   * Gets an instance of the framework
   * {@link android.hardware.biometrics.BiometricManager} class.
   *
   * @param context The application or activity context.
   * @return An instance of {@link android.hardware.biometrics.BiometricManager}.
   */
  @Nullable
  static android.hardware.biometrics.BiometricManager create(@NonNull Context context) {
   return context.getSystemService(android.hardware.biometrics.BiometricManager.class);
  }

  /**
   * Calls {@link android.hardware.biometrics.BiometricManager#canAuthenticate()} for the
   * given biometric manager.
   *
   * @param biometricManager An instance of
   *                         {@link android.hardware.biometrics.BiometricManager}.
   * @return The result of
   * {@link android.hardware.biometrics.BiometricManager#canAuthenticate()}.
   */
  @SuppressLint("WrongConstant")
  @AuthenticationStatus
  static int canAuthenticate(
          @NonNull android.hardware.biometrics.BiometricManager biometricManager) {
   return biometricManager.canAuthenticate();
  }

  /**
   * Checks for and returns the hidden {@link android.hardware.biometrics.BiometricManager}
   * method {@code canAuthenticate(CryptoObject)} via reflection.
   *
   * @return The method {@code BiometricManager#canAuthenticate(CryptoObject)}, if present.
   */
  @SuppressWarnings("JavaReflectionMemberAccess")
  @Nullable
  static Method getCanAuthenticateWithCryptoMethod() {
   try {
    return android.hardware.biometrics.BiometricManager.class.getMethod(
            "canAuthenticate",
            android.hardware.biometrics.BiometricPrompt.CryptoObject.class);
   } catch (NoSuchMethodException e) {
    return null;
   }
  }
 }
}

